package com.dongnam.quanly.web.privilege;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.faces.application.FacesMessage;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ManagedProperty;
import javax.faces.bean.ViewScoped;

import org.hibernate.LazyInitializationException;
import org.hibernate.Session;
import org.hibernate.exception.ConstraintViolationException;
import org.primefaces.context.RequestContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.dongnam.quanly.common.ActionCategory;
import com.dongnam.quanly.common.ActionType;
import com.dongnam.quanly.common.ObjectType;
import com.dongnam.quanly.common.ScreenId;
import com.dongnam.quanly.common.ScreenType;
import com.dongnam.quanly.common.controller.UserRoleController;
import com.dongnam.quanly.common.dao.UserRightDao;
import com.dongnam.quanly.common.dao.UserRoleDao;
import com.dongnam.quanly.common.exceptions.DatabaseException;
import com.dongnam.quanly.common.model.UserRight;
import com.dongnam.quanly.common.model.UserRole;
import com.dongnam.quanly.common.model.UserRight.AccessLevel;
import com.dongnam.quanly.common.persistence.HibernateUtil;
import com.dongnam.quanly.web.DiffComparator;
import com.dongnam.quanly.web.JSFUtility;
import com.dongnam.quanly.web.UserBean;
import com.dongnam.quanly.web.audit.AuditLogger;
import com.dongnam.quanly.web.util.LocaleMessages;
import com.dongnam.quanly.web.util.MessageUtil;

@ManagedBean(name = "UserRolesBean")
@ViewScoped
public class UserRolesBean implements Serializable {
	private static final long serialVersionUID = 1L;
	private static final Logger logger = LoggerFactory.getLogger(UserRolesBean.class);

	@ManagedProperty(value = "#{UserBean}")
	private UserBean userBean;

	private List<UserRole> userRoles;
	private List<UserRight> selectedRights; // rights current role has

	private UserRole selectedRole;
	private UserRight selectedRight;
	private String selectedRoleId;

	private UserRole prev;
	private Map<ScreenType, AccessLevel> prevRights;

	private List<String> levelList;

	private boolean createNew;

	public UserRolesBean() {
		setUserRoles(new UserRoleController().findAll());
	}

	// actions //

	public void selectRole(UserRole role) {
		selectedRole = role;
		refreshRights();
	}

	public void addRole() {
		selectedRole = new UserRole();
	}

	public void deleteRole() {
		logger.debug("deleteRole: {}", selectedRole);
		if (selectedRole != null && selectedRole.getRoleId().equals("Superrole")) {
			// Not allow to delete super role
			addErrorMsg("general_delete_failed", LocaleMessages.getString("privilege_roleDeleteSuperrole"), new Date());
			AuditLogger.logAuditTrail(ScreenId.PRIVILEGE_USER, ActionCategory.PRIVILEGE, ActionType.DELETE, false,
					ObjectType.UserRole, selectedRole.getRoleId(),
					LocaleMessages.getString("privilege_roleDeleteSuperrole"), null, null);
			return;
		}
		boolean isTransacted = HibernateUtil.getCurrentSession().getTransaction().isActive();
		Session session = isTransacted ? HibernateUtil.getCurrentSession() : HibernateUtil.startCurrentSession();

		try {
			// new UserRoleController().makeTransient(selectedRole);
			new UserRoleDao().makeTransient(selectedRole);
			AuditLogger.persistAuditTrail(ScreenId.PRIVILEGE_USER, ActionCategory.PRIVILEGE, ActionType.DELETE, true,
					ObjectType.UserRole, selectedRole.getRoleId(), null, null, null);
			if (!isTransacted) {
				session.getTransaction().commit();
			}
		} catch (DatabaseException dbe) {
			if (!isTransacted && session.isOpen() && !session.getTransaction().wasCommitted())
				session.getTransaction().rollback();
			addErrorMsg("general_delete_failed", MessageUtil.getDescriptionOfDBException(dbe), new Date());
			AuditLogger.logAuditTrail(ScreenId.PRIVILEGE_USER, ActionCategory.PRIVILEGE, ActionType.DELETE, false,
					ObjectType.UserRole, selectedRole.getRoleId(), MessageUtil.getDescriptionOfDBException(dbe), null,
					null);
			MessageUtil.logDBException("Delete role", dbe);
			refreshRoles();
			return;
		} catch (Exception e) {
			if (!isTransacted && session.isOpen() && !session.getTransaction().wasCommitted())
				session.getTransaction().rollback();
			String message = LocaleMessages.getString("privilege_roleDeleteError", selectedRole.getRoleId());
			JSFUtility.setJSFFacesMessage(FacesMessage.SEVERITY_ERROR, message, null);
			AuditLogger.logAuditTrail(ScreenId.PRIVILEGE_USER, ActionCategory.PRIVILEGE, ActionType.DELETE, false,
					ObjectType.UserRole, selectedRole.getRoleId(), message, null, null);
			refreshRoles();
			return;
		}
		addInfoMsg("general_delete_success", selectedRole.getRoleId());
		selectedRole = null;
		selectedRights = null;
		refreshRoles();

	}

	public void saveRole() {
		logger.debug("saveRole: {}", selectedRole);
		RequestContext context = RequestContext.getCurrentInstance();
		if (createNew && failsValidation(selectedRole)) {
			selectedRole = new UserRole();
			context.addCallbackParam("opSuccess", false);
			return;
		}
		// if (!createNew)
		// prev = new UserRoleController().findById(selectedRole.getRoleId(),
		// false);
		selectedRole.setLastUpdatedBy(userBean.getUser().getUserId());
		try {
			new UserRoleController().makePersistent(selectedRole);
		} catch (Exception e) {
			if (e.getCause() instanceof ConstraintViolationException) {
				String message = LocaleMessages.getString("privilege_rolenameexist", selectedRole.getRoleName());
				JSFUtility.setJSFFacesMessage(FacesMessage.SEVERITY_ERROR, message, message);
				AuditLogger.logAuditTrail(ScreenId.PRIVILEGE_USER, ActionCategory.PRIVILEGE,
						createNew ? ActionType.CREATE : ActionType.UPDATE, false, ObjectType.UserRole, createNew ? null
								: selectedRole.getRoleId(), message, null, null);
				prev = null;
				if (createNew)
					selectedRole = new UserRole();
				if (!createNew)
					refreshRoles();
			} else {
				MessageUtil.logDBException("Save role", e);
				addErrorMsg("general_add_edit_fail", MessageUtil.getDescriptionOfDBException(e), new Date());
				AuditLogger.logAuditTrail(ScreenId.PRIVILEGE_USER, ActionCategory.PRIVILEGE,
						createNew ? ActionType.CREATE : ActionType.UPDATE, false, ObjectType.UserRole, createNew ? null
								: selectedRole.getRoleId(), MessageUtil.getDescriptionOfDBException(e), null, null);
				prev = null;
				selectedRole = new UserRole();
				refreshRoles();
			}
			context.addCallbackParam("opSuccess", false);
			return;
		}

		refreshRoles();

		boolean isTransacted = HibernateUtil.getCurrentSession().getTransaction().isActive();
		Session session = isTransacted ? HibernateUtil.getCurrentSession() : HibernateUtil.startCurrentSession();

		try {
			new UserRoleDao().makePersistent(selectedRole);
			// case when role is newly created
			if (selectedRole.getUserRights() == null || selectedRole.getUserRights().isEmpty()) {
				for (ScreenType type : ScreenType.getEnum("A")) {
					// logger.debug("screenType: {}",type.toString());
					selectedRole
							.addUserRight(new UserRight(type, UserRight.AccessLevel.NONE, selectedRole.getRoleId()));
				}
			}
			String[] differences = { "", "" };
			if (!createNew) {
				DiffComparator diff = new DiffComparator();
				differences = diff.compare(ObjectType.UserRole, prev, selectedRole);
			}
			AuditLogger.persistAuditTrail(ScreenId.PRIVILEGE_USER, ActionCategory.PRIVILEGE, ActionType.UPDATE, true,
					ObjectType.UserRole, selectedRole.getRoleId(), null, differences[0], differences[1]);
			if (!isTransacted) {
				session.getTransaction().commit();
			}
		} catch (Throwable e) {
			if (!isTransacted && session.isOpen() && !session.getTransaction().wasCommitted())
				session.getTransaction().rollback();
			MessageUtil.logDBException("Save role", new Exception(e));
			addErrorMsg("general_add_edit_fail", MessageUtil.getDescriptionOfDBException(new Exception(e)), new Date());
			AuditLogger.logAuditTrail(ScreenId.PRIVILEGE_USER, ActionCategory.PRIVILEGE, ActionType.UPDATE, false,
					ObjectType.UserRole, selectedRole.getRoleId(),
					MessageUtil.getDescriptionOfDBException(new Exception(e)), null, null);
			refreshRoles();
			refreshRights();
			prev = null;
			selectedRole = null;
			context.addCallbackParam("opSuccess", false);
			return;
		}
		addInfoMsg("general_add_edit_success", selectedRole.getRoleId());
		refreshRoles();
		refreshRights();
		prev = null;
		context.addCallbackParam("opSuccess", true);

	}

	public void selectRight(UserRight right) {
		selectedRight = right;
	}

	public void editRightsInRole() {
		logger.debug("editRights:{} in role{}", selectedRights, selectedRole);
		// logger.debug("prevRights: (editing) {}",prevRights);
		// selectedRole.setUserRights(new HashSet<UserRight>(selectedRights));
		boolean isTransacted = HibernateUtil.getCurrentSession().getTransaction().isActive();
		Session session = isTransacted ? HibernateUtil.getCurrentSession() : HibernateUtil.startCurrentSession();

		try {
			//
			// dao.clear();
			// new UserRoleController().makePersistent(selectedRole);
			selectedRole.setLastUpdatedBy(userBean.getUser().getUserId());
			new UserRoleDao().makePersistent(selectedRole);
			String[] differences = compareRights(prevRights, selectedRights);
			AuditLogger.persistAuditTrail(ScreenId.PRIVILEGE_USER, ActionCategory.PRIVILEGE, ActionType.UPDATE, true,
					ObjectType.UserRole, selectedRole.getRoleId(), null, "UserRight modified:<br/> " + differences[0],
					"UserRight modified: <br/>" + differences[1]);
			if (!isTransacted) {
				session.getTransaction().commit();
			}
		} catch (Exception e) {
			if (!isTransacted && session.isOpen() && !session.getTransaction().wasCommitted())
				session.getTransaction().rollback();
			addErrorMsg("general_add_edit_fail", MessageUtil.getDescriptionOfDBException(e), new Date());
			MessageUtil.logDBException("Edit right", e);
			AuditLogger.logAuditTrail(ScreenId.PRIVILEGE_USER, ActionCategory.PRIVILEGE, ActionType.UPDATE, false,
					ObjectType.UserRole, selectedRole.getRoleId(), MessageUtil.getDescriptionOfDBException(e), null,
					null);
			refreshRoles();
			refreshRights();
			return;
		}
		addInfoMsg("privilege_rightEdit", null);
		refreshRoles();
		refreshRights();
	}

	private String[] compareRights(Map<ScreenType, AccessLevel> prev, List<UserRight> currList) {
		String[] diff = { "", "" };
		if (prev == null || currList == null)
			return diff;
		for (int i = 0; i < prev.size(); i++) {
			if (!prev.get(currList.get(i).getTypeId()).equals(currList.get(i).getAccessLevel())) {
				diff[0] = diff[0] + currList.get(i).getTypeId() + "=" + prev.get(currList.get(i).getTypeId());
				diff[1] = diff[1] + currList.get(i).getTypeId() + "=" + currList.get(i).getAccessLevel();
				if (i < prev.size() - 1) {
					diff[0] = diff[0] + ";";
					diff[1] = diff[1] + ";";
				}

			}
		}
		return diff;
	}

	public void refreshRoles() {
		setUserRoles(new UserRoleController().findAll());
		if (selectedRole != null && selectedRole.getRoleId() != null && !selectedRole.getRoleId().equals(""))
			setSelectedRole(new UserRoleController().findById(selectedRole.getRoleId(), false));
	}

	public void refreshRightsWhenScreenChanges() {
		List<ScreenType> newTypeList = new ArrayList<ScreenType>();
		List<ScreenType> oldTypeList = new ArrayList<ScreenType>();
		List<ScreenType> removeTypeList = new ArrayList<ScreenType>();
		boolean isTransacted = HibernateUtil.getCurrentSession().getTransaction().isActive();
		Session session = isTransacted ? HibernateUtil.getCurrentSession() : HibernateUtil.startCurrentSession();

		try {
			new UserRoleDao().makePersistent(selectedRole);
			for (ScreenType type : ScreenType.getEnum("A")) {
				newTypeList.add(type);
			}
			logger.debug("newlist: {}", newTypeList);

			for (UserRight right : selectedRole.getUserRights()) {
				if (right.getTypeId().active.equals("A"))
					oldTypeList.add(right.getTypeId());
			}
			logger.debug("oldlist: {}", oldTypeList);
			for (ScreenType type : ScreenType.getEnum("I")) {
				removeTypeList.add(type);
			}
			logger.debug("removelist: {}", removeTypeList);
			newTypeList.removeAll(oldTypeList); // set difference, list of
												// typeids to be saved to db
			logger.debug("after, newlist: {}", newTypeList);
			// case when new screens are made or removed in ScreenType, but not
			// reflected in db yet
			if (!newTypeList.isEmpty()) {
				logger.debug("New screens are made.");

				// add new page
				for (ScreenType newType : newTypeList) {
					UserRight right = new UserRight(newType, UserRight.AccessLevel.NONE, selectedRole.getRoleId());
					selectedRole.addUserRight(right);
				}
				selectedRole.setLastUpdatedBy(userBean.getUser().getUserId());
				AuditLogger.persistAuditTrail(ScreenId.PRIVILEGE_USER, ActionCategory.PRIVILEGE, ActionType.UPDATE,
						true, ObjectType.UserRole, selectedRole.getRoleId(), null, "", "UserRights: " + newTypeList
								+ " added");
			}
			if (!removeTypeList.isEmpty()) {
				logger.debug("Some screens have been removed. ");
				for (ScreenType remove : removeTypeList) {
					List<UserRight> removeRight = new UserRightDao().findByType(remove);
					List<UserRight> actualRemove = new ArrayList<UserRight>();
					for (UserRight right : removeRight)
						if (selectedRole.getUserRights().contains(right)) {
							selectedRole.removeUserRight(right);
							actualRemove.add(right);
						}
					AuditLogger.persistAuditTrail(ScreenId.PRIVILEGE_USER, ActionCategory.PRIVILEGE, ActionType.UPDATE,
							true, ObjectType.UserRole, selectedRole.getRoleId(), null, "", "UserRights: "
									+ actualRemove + " removed");
					removeRight.clear();
					actualRemove.clear();
				}
			}
			selectedRights = new ArrayList<UserRight>(selectedRole.getUserRights());
			Collections.sort(selectedRights);
			if (!isTransacted) {
				session.getTransaction().commit();
			}
		} catch (Throwable e) {
			if (!isTransacted && session.isOpen() && !session.getTransaction().wasCommitted())
				session.getTransaction().rollback();
			String message = addErrorMsg("privilege_rightRefresh_error");
			AuditLogger.logAuditTrail(ScreenId.PRIVILEGE_USER, ActionCategory.PRIVILEGE, ActionType.UPDATE, false,
					ObjectType.UserRole, selectedRole.getRoleId(), message, null, null);
			logger.error("Exception: ", new Exception(e));
			refreshRoles();
			return;
		}
		if (!newTypeList.isEmpty()) {
			addInfoMsg("privilege_rightAdd", null);
		}
		if (!removeTypeList.isEmpty()) {
			addInfoMsg("privilege_rightRefresh", null);
		}
		if (newTypeList.isEmpty() && removeTypeList.isEmpty())
			addInfoMsg("privilege_rightnochanges", null);
		oldTypeList.clear();
		newTypeList.clear();
		removeTypeList.clear();
	}

	public void refreshRights() {
		// prevRights = new UserRightController().findByRole(selectedRole);
		ArrayList<UserRight> temp = null;
		boolean isTransacted = HibernateUtil.getCurrentSession().getTransaction().isActive();
		Session session = isTransacted ? HibernateUtil.getCurrentSession() : HibernateUtil.startCurrentSession();

		try {
			new UserRoleDao().makePersistent(selectedRole);
			temp = new ArrayList<UserRight>(selectedRole.getUserRights());
			Collections.sort(temp);
			if (prevRights == null)
				prevRights = new HashMap<ScreenType, AccessLevel>();
			else
				prevRights.clear();
			for (UserRight right : temp)
				prevRights.put(right.getTypeId(), right.getAccessLevel());
			logger.debug("prev rights: {}", prevRights);
			if (!isTransacted) {
				session.getTransaction().commit();
			}
		} catch (LazyInitializationException e) {
			if (!isTransacted && session.isOpen() && !session.getTransaction().wasCommitted())
				session.getTransaction().rollback();
			return;
			// refreshRightsWhenScreenChanges();
		} catch (IllegalArgumentException e) {
			if (!isTransacted && session.isOpen() && !session.getTransaction().wasCommitted())
				session.getTransaction().rollback();
			return;
			// refreshRightsWhenScreenChanges();
		} catch (Throwable e) {
			if (!isTransacted && session.isOpen() && !session.getTransaction().wasCommitted())
				session.getTransaction().rollback();
			logger.debug("Exception: ", new Exception(e));
			return;
		}
		selectedRights = temp;
		logger.debug("rights: {}", selectedRights);
	}

	private String addInfoMsg(String msgKey, String id) {
		String message = LocaleMessages.getString(msgKey, id);
		JSFUtility.setJSFFacesMessage(FacesMessage.SEVERITY_INFO, message, message);
		return message;
	}

	private String addErrorMsg(String msgKey) {
		String message = LocaleMessages.getString(msgKey);
		JSFUtility.setJSFFacesMessage(FacesMessage.SEVERITY_ERROR, message, message);
		return message;
	}

	private String addErrorMsg(String msgKey, String e, Date date) {
		String message = LocaleMessages.getString(msgKey, e, date);
		JSFUtility.setJSFFacesMessage(FacesMessage.SEVERITY_ERROR, message, message);
		return message;
	}

	// validation //

	public boolean failsValidation(UserRole role) {
		return roleExistsAlready(role);
	}

	public boolean roleExistsAlready(UserRole newRole) {
		for (UserRole role : userRoles) {
			if (role.getRoleId().equals(newRole.getRoleId()) && role != newRole) {
				String message = LocaleMessages.getString("privilege_roleExistsAlready", selectedRole.getRoleId());
				JSFUtility.setJSFFacesMessage(FacesMessage.SEVERITY_ERROR, message, message);
				AuditLogger.logAuditTrail(ScreenId.PRIVILEGE_USER, ActionCategory.PRIVILEGE, ActionType.CREATE, false,
						ObjectType.UserRole, selectedRole.getRoleId(), message, null, null);
				return true; // roleid exists already, bad
			}
		}
		return false; // roleid doesnt exist yet, ok
	}

	// getters setters //

	public List<UserRight> getSelectedRights() {
		boolean isTransacted = HibernateUtil.getCurrentSession().getTransaction().isActive();
		Session session = isTransacted ? HibernateUtil.getCurrentSession() : HibernateUtil.startCurrentSession();

		try {
			new UserRoleDao().makePersistent(selectedRole);
			if (selectedRights == null && selectedRole != null && selectedRole.getUserRights() != null) {
				selectedRights = new ArrayList<UserRight>(selectedRole.getUserRights());
			}
			Collections.sort(selectedRights);
			if (!isTransacted) {
				session.getTransaction().commit();
			}
		} catch (Throwable e) {
			if (!isTransacted && session.isOpen() && !session.getTransaction().wasCommitted())
				session.getTransaction().rollback();
			// addErrorMsg("error_selecting_roles");
		}
		return selectedRights;
	}

	public void setSelectedRights(List<UserRight> selectedRights) {
		this.selectedRights = selectedRights;
	}

	public List<UserRole> getUserRoles() {
		if (userRoles == null) {
			return userRoles = new UserRoleController().findAll();
		}
		return userRoles;
	}

	public void setUserRoles(List<UserRole> userRoles) {
		this.userRoles = userRoles;
	}

	public UserRole getSelectedRole() {
		return selectedRole;
	}

	public void setSelectedRole(UserRole selectedRole) {
		this.selectedRole = selectedRole;
	}

	public UserRight getSelectedRight() {
		return selectedRight;
	}

	public void setSelectedRight(UserRight selectedRight) {
		this.selectedRight = selectedRight;
	}

	public List<String> getLevelList() {
		if (levelList == null) {
			levelList = new ArrayList<String>();
			for (AccessLevel level : UserRight.AccessLevel.values()) {
				levelList.add(level.toString());
			}
		}
		return levelList;
	}

	public void setUserBean(UserBean userBean) {
		this.userBean = userBean;
	}

	public boolean isCreateNew() {
		return createNew;
	}

	public void setCreateNew(boolean createNew) {
		this.createNew = createNew;
	}

	public void setSelectedRoleId(String selectedRoleId) {
		logger.debug("selectedroleid set");
		this.selectedRoleId = selectedRoleId;
		if (userRoles == null)
			setUserRoles(new UserRoleController().findAll());
		for (UserRole role : userRoles) {
			if (role.getRoleId().equals(selectedRoleId)) {
				selectRole(role);
				logger.debug("Role {} selected", role);
			}
		}
	}

	public String getSelectedRoleId() {
		return selectedRoleId;
	}
}
